In [1]:
from uniswap_fee_and_divergence import *
In [2]:
# Import pickle file
import pickle 

with open('backtest_results.pickle', 'rb') as handle:
    results = pickle.load(handle)
In [4]:
import plotly.graph_objects as go
import plotly.io as pio
pio.renderers.default='notebook'

def analyse_results(results):
    # Initialize empty DataFrame to hold summary stats
    summary_stats = pd.DataFrame()
    fig = go.Figure()

    # Loop through the results
    for result in results:
        # Get general info
        range_pct = result["range_pct"]
        is_hedged = result["is_hedged"]
        backtester = result["backtester"]

        # Get backtest DataFrame
        backtest_df = backtester.backtest_df

        # Calculate key stats
        fees_generated = backtest_df["fee"].sum()
        losses_due_to_divergence = backtest_df["divergence"].sum()
        pnl_total_with_fees = backtest_df.groupby("id")["pnl_total_with_fees"].last().sum()
        roi = backtester.roi
        max_dd = backtester.max_dd
        # Append stats to summary DataFrame
        summary_stats = pd.concat([summary_stats, pd.DataFrame({
            "Range Percentage": range_pct,
            "Is Hedged": is_hedged,
            "Number of positions": len(backtest_df.id.unique()),
            "Total Fees Generated": fees_generated,
            "Losses Due to Divergence": losses_due_to_divergence,
            "Total PNL with Fees": pnl_total_with_fees,
            "ROI": roi,
            "DD": max_dd,
            "ID": results.index(result)
        }, index=[0])])

        # Generate line for each backtest
        fig.add_trace(go.Scatter(
            x=backtest_df.index,
            y=backtest_df['net_usd_capital'],
            mode='lines',
            name=f'{range_pct}%, {"Hedged" if is_hedged else "Unhedged"}',
            #hovertemplate = '<b>Capital</b>: %{y:.2f}<extra></extra>'
        ))

    # Edit layout
    fig.update_layout(title='Backtest Capital Over Time for Different Configurations',
                      height=600,
                    xaxis_title='Time',
                    yaxis_title='Net USD Capital',
                    hovermode="x unified")
    
    fig.show()
    
    # Set index to the configurations for easier referencing
    summary_stats.set_index(["Range Percentage", "Is Hedged"], inplace=True)

    return summary_stats

summary_stats = analyse_results(results)

# Displaying the summary sorted by ROI
summary_stats.sort_values(by='ROI', ascending=False)
Out[4]:
Number of positions Total Fees Generated Losses Due to Divergence Total PNL with Fees ROI DD ID
Range Percentage Is Hedged
0.25 True 5427 3.045300e+06 -12.967646 367941.401915 157.673319 0.456666 24
False 5427 3.045300e+06 -19.306082 95305.687183 95.305687 0.526607 25
0.50 True 3349 1.522650e+06 -11.060791 175150.090926 80.303426 0.488346 22
0.75 True 2281 1.015100e+06 -10.327435 113217.506109 51.234421 0.540954 20
1.00 True 1658 7.613249e+05 -9.445027 89710.865240 41.840826 0.575305 18
0.50 False 3349 1.522650e+06 -16.769627 30933.574210 30.933574 0.608637 23
2.00 True 694 3.806624e+05 -4.406666 45732.541752 14.829102 0.625625 16
0.75 False 2281 1.015100e+06 -15.555946 7401.576860 7.401577 0.677577 21
3.00 True 397 2.537750e+05 -9.343309 28719.668960 0.909471 0.634923 14
1.00 False 1658 7.613249e+05 -14.536074 678.959142 0.678959 0.700314 19
4.00 True 270 1.903312e+05 -15.913529 20886.601208 -4.408404 0.658010 12
6.00 True 131 1.268875e+05 -24.717941 15276.492192 -8.258188 0.635138 8
5.00 True 187 1.522650e+05 -14.110463 14447.637649 -10.557003 0.634995 10
7.00 True 105 1.087607e+05 -32.574201 8627.287566 -15.938392 0.636237 6
9.00 True 66 8.459165e+04 14.890108 9694.161175 -16.312923 0.580023 2
2.00 False 694 3.806624e+05 -10.881516 -18693.215035 -18.693215 0.711620 17
8.00 True 92 9.516561e+04 -26.127067 5298.602725 -20.605073 0.623922 4
10.00 True 63 7.613249e+04 -24.565869 1048.318563 -24.396788 0.604139 0
3.00 False 397 2.537750e+05 -15.821403 -28648.366485 -28.648366 0.699935 15
4.00 False 270 1.903312e+05 -22.023441 -30425.783004 -30.425783 0.724144 13
6.00 False 131 1.268875e+05 -32.427186 -31702.711884 -31.702712 0.677762 9
5.00 False 187 1.522650e+05 -22.444481 -35212.912365 -35.212912 0.676395 11
9.00 False 66 8.459165e+04 -7.029798 -36165.767882 -36.165768 0.605812 3
7.00 False 105 1.087607e+05 -39.596866 -37328.381575 -37.328382 0.661893 7
8.00 False 92 9.516561e+04 -38.529036 -40540.446266 -40.540446 0.654174 5
10.00 False 63 7.613249e+04 -36.500826 -43285.531112 -43.285531 0.603830 1
In [1]:
# Read markdown file and display
with open('README.md', 'r') as file:
    md = file.read()

from IPython.display import Markdown
Markdown(md)
Out[1]:

Uniswap V3 Strategy Backtest Results¶

Executive Summary¶

This internal document presents the result of our backtest on a Uniswap V3 strategy for the ETH/USDC pool with the aim to assess the impact of different configurations on performance, specifically focusing on the range and whether a hedging strategy was implemented.

Assumption for backtesting¶

During this experiment due to the lack of data, it has been difficult to quite exactly scale up potiential fee revenue earned by unit of volume in the pool. Due to the lack of liquidity density data for each datapoint, we had to make an assumption in order to linearly scale this fee variable:

  • We are not significant liquidity providers in the pool based on own_capital_usd / TVL ratio.

  • Therefore related to the first point, even with narrow ranges we are not owning significant amount of the range

If both of these assumptions are taken as true, we can linearly scale our rewards from a position we held open for 10 days of $100 face value, which earned $2.01 in that time period. If these assumptions are false in real life than the fee earner per volume unit would scale much more like a log function with diminishing returns as we are making the range narrower and owning more and more of the liquidity range.

Findings¶

Our findings indicate that both range percentage and hedging are vital factors influencing the strategy's performance. The key results of our backtest are summarized below:

  • The configuration that achieved the highest ROI (157.67%) had a range percentage of 0.25 and utilized hedging. This configuration also had the lowest drawdown (45.7%), indicating a relatively lower risk compared to other configurations.

  • Overall, configurations with hedging resulted in a better ROI than those without. For example, when a range of 0.25% was used, the hedged strategy achieved a significantly higher ROI (157.67%) compared to the unhedged strategy (95.31%). This suggests that hedging could effectively offset some of the losses due to the divergence, leading to better overall performance.

  • Our analysis also shows that a larger range doesn't necessarily equate to better performance. The configuration with the highest range (10%) had one of the lowest ROIs, whether hedged (-24.40%) or unhedged (-43.29%). This could be due to the fact that larger ranges may result in lower concentrated liquidity multipler, thus generating fewer fees.

  • Conversely, a smaller range, specifically 0.25%, achieved the best results in both hedged and unhedged scenarios. This suggests that a more frequent trading approach could be advantageous, given the fee structure of Uniswap V3.

  • As expected, hedging helped decrease the directional risk. For each range, hedged strategies consistently exhibited a lower drawdown than their unhedged counterparts. This clearly indicates the risk-mitigation benefit of hedging.

Based on these findings, we suggest further exploration of the following additional strategies to increase return and decrease directional risks:

  1. Adjusting the range more dynamically: Instead of sticking to a single static range for the entire duration, the strategy could adjust its range based on market conditions. For example, it could use a larger range in more volatile conditions and a smaller one in less volatile conditions.

  2. Incorporating predictive models: Machine learning models could be utilized to predict the future price of ETH and adjust the range or the hedging strategy accordingly.

  3. Implementing stop-loss rules: To further control risk, the strategy could close the position and hedge it once the loss reaches a certain threshold.

We hope these insights are helpful in refining our Uniswap V3 strategy. We recommend conducting additional backtests incorporating the strategies suggested above for further optimization.

Notes:¶

  • Range percentages are meant to be doubles, as they indicate what percentage from current price we are positioning our upper and lower range.

Latest version of the nalysis notebook can be found here:¶

  • HTML
  • IPynb

Summary of the results:¶

Number of positions Total Fees Generated Losses Due to Divergence Total PNL with Fees ROI DD ID
(10.0, True) 63 76132.5 -24.5659 1048.32 -24.3968 0.604139 0
(10.0, False) 63 76132.5 -36.5008 -43285.5 -43.2855 0.60383 1
(9.0, True) 66 84591.7 14.8901 9694.16 -16.3129 0.580023 2
(9.0, False) 66 84591.7 -7.0298 -36165.8 -36.1658 0.605812 3
(8.0, True) 92 95165.6 -26.1271 5298.6 -20.6051 0.623922 4
(8.0, False) 92 95165.6 -38.529 -40540.4 -40.5404 0.654174 5
(7.0, True) 105 108761 -32.5742 8627.29 -15.9384 0.636237 6
(7.0, False) 105 108761 -39.5969 -37328.4 -37.3284 0.661893 7
(6.0, True) 131 126887 -24.7179 15276.5 -8.25819 0.635138 8
(6.0, False) 131 126887 -32.4272 -31702.7 -31.7027 0.677762 9
(5.0, True) 187 152265 -14.1105 14447.6 -10.557 0.634995 10
(5.0, False) 187 152265 -22.4445 -35212.9 -35.2129 0.676395 11
(4.0, True) 270 190331 -15.9135 20886.6 -4.4084 0.65801 12
(4.0, False) 270 190331 -22.0234 -30425.8 -30.4258 0.724144 13
(3.0, True) 397 253775 -9.34331 28719.7 0.909471 0.634923 14
(3.0, False) 397 253775 -15.8214 -28648.4 -28.6484 0.699935 15
(2.0, True) 694 380662 -4.40667 45732.5 14.8291 0.625625 16
(2.0, False) 694 380662 -10.8815 -18693.2 -18.6932 0.71162 17
(1.0, True) 1658 761325 -9.44503 89710.9 41.8408 0.575305 18
(1.0, False) 1658 761325 -14.5361 678.959 0.678959 0.700314 19
(0.75, True) 2281 1.0151e+06 -10.3274 113218 51.2344 0.540954 20
(0.75, False) 2281 1.0151e+06 -15.5559 7401.58 7.40158 0.677577 21
(0.5, True) 3349 1.52265e+06 -11.0608 175150 80.3034 0.488346 22
(0.5, False) 3349 1.52265e+06 -16.7696 30933.6 30.9336 0.608637 23
(0.25, True) 5427 3.0453e+06 -12.9676 367941 157.673 0.456666 24
(0.25, False) 5427 3.0453e+06 -19.3061 95305.7 95.3057 0.526607 25